using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;

namespace Craps
{
    /// <summary>
    /// This is the main type for your game
    /// </summary>
    public class CrapsGame : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;
        GameObject background;

        GameObject die1, die2;
        int die1num, die2num;
        uint bankroll;
        GameObject redchip, bluechip, greenchip, blackchip;
        GameObject[] dice;
        bool rollHandled;

        List<GameObject> chips;

        Rectangle[] passLineRect;
        Rectangle comeRect;
        Rectangle sixRect;
        Rectangle eightRect;

#if !XBOX
        MouseState previousMouseState;
        KeyboardState previousKeyboardState;
#endif

        int point;
        const float accel = 0.90f;
        Random rand = new Random();

        CrapsSystem crapsSystem;

        SpriteFont font;

        public CrapsGame()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
        }

        /// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected override void Initialize()
        {
            
#if !XBOX
            previousMouseState = Mouse.GetState();
            previousKeyboardState = Keyboard.GetState();
#endif
            point = 0;
            chips = new List<GameObject>();

            passLineRect = new Rectangle[2];
            passLineRect[0].X = 191;
            passLineRect[0].Y = 383;
            passLineRect[0].Width = 437;
            passLineRect[0].Height = 43;
            passLineRect[1].X = 48;
            passLineRect[1].Y = 70;
            passLineRect[1].Width = 48;
            passLineRect[1].Height = 234;

            comeRect = new Rectangle(156, 200, 472, 75);

            sixRect = new Rectangle(393, 120, 74, 62);
            eightRect = new Rectangle(474, 121, 78, 60);

            crapsSystem = new CrapsSystem();
            bankroll = 100;
            rollHandled = true;

            base.Initialize();
        }

        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);

            background = new GameObject(Content.Load<Texture2D>(@"Sprites\Crapslayout"));
            background.scale = new Vector2(0.70f, 0.70f);
            graphics.PreferredBackBufferHeight = background.rectangle.Height;
            graphics.PreferredBackBufferWidth = background.rectangle.Width;
            background.center.X = graphics.PreferredBackBufferWidth / 2;
            background.center.Y = graphics.PreferredBackBufferHeight / 2;
            
            dice = new GameObject[6];
            for (int x = 0; x < 6; ++x)
            {
                dice[x] = new GameObject(Content.Load<Texture2D>(String.Format(@"Sprites\dice{0}", (x + 1))));
            }

            die1 = new GameObject(dice[0]);
            die2 = new GameObject(dice[1]);

            redchip = new GameObject(Content.Load<Texture2D>(@"Sprites\redchip"));
            bluechip = new GameObject(Content.Load<Texture2D>(@"Sprites\bluechip"));
            greenchip = new GameObject(Content.Load<Texture2D>(@"Sprites\greenchip"));
            blackchip = new GameObject(Content.Load<Texture2D>(@"Sprites\blackchip"));

            die1.visible = false;
            die2.visible = false;
            die1.alive = false;
            die2.alive = false;

            redchip.scale = new Vector2(.3f, .3f);
            bluechip.scale = new Vector2(.3f, .3f);
            greenchip.scale = new Vector2(.3f, .3f);
            blackchip.scale = new Vector2(.3f, .3f);

            font = Content.Load<SpriteFont>(@"Fonts\Kootenay");

            this.IsMouseVisible = true;
            
            graphics.ApplyChanges();
        }

        /// <summary>
        /// UnloadContent will be called once per game and is the place to unload
        /// all content.
        /// </summary>
        protected override void UnloadContent()
        {
            // TODO: Unload any non ContentManager content here
        }

        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime gameTime)
        {
            // Allows the game to exit
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                this.Exit();

#if !XBOX
            MouseState mouseState = Mouse.GetState();

            if (previousMouseState.LeftButton == ButtonState.Released && 
                mouseState.LeftButton == ButtonState.Pressed)
            {                
                AddChip(new Vector2(mouseState.X, mouseState.Y));
            }

            previousMouseState = mouseState;

            KeyboardState keyboardState = Keyboard.GetState();

            if (crapsSystem.haveBets() && 
                keyboardState.IsKeyDown(Keys.Space) && previousKeyboardState.IsKeyUp(Keys.Space)
                && rollHandled == true)
            {
                RollDice();
                rollHandled = false;
            }

            if (DiceDone())
            {
                // TODO run this once
                if (!rollHandled)
                {
                    crapsSystem.handleRoll(die1num, die2num);
                    removeChips();
                    addWinnings();
                    point = crapsSystem.getPoint();
                    rollHandled = true;
                }
            }
            else
            {
                UpdateDice();
            }

            previousKeyboardState = keyboardState;
#endif
            base.Update(gameTime);
        }

        private void addWinnings()
        {
            Dictionary<CrapsSystem.Bet, uint> bets = crapsSystem.getBets();
            Dictionary<CrapsSystem.Bet, uint> oldbets = crapsSystem.getOldBets();
            foreach (KeyValuePair<CrapsSystem.Bet, uint> pair in oldbets)
            {
                // TODO: Add chips to table
                if (oldbets[pair.Key] < pair.Value)
                {
                    bankroll += pair.Value - oldbets[pair.Key];
                    crapsSystem.removeBet(pair.Key, 
                        (pair.Value - oldbets[pair.Key]));
                }
            }
        }

        private void removeChips()
        {
            Dictionary<CrapsSystem.Bet, uint> bets = crapsSystem.getBets();
            Dictionary<CrapsSystem.Bet, uint> oldBets = crapsSystem.getOldBets();
            foreach (KeyValuePair<CrapsSystem.Bet, uint> pair in oldBets)
            {
                if (!bets.ContainsKey(pair.Key))
                {
                    removeChips(pair.Key);
                }

                // TODO: Remove chips that win and shouldn't ride
            }
        }

        private void removeChips(CrapsSystem.Bet bet)
        {
            List<GameObject> removals = new List<GameObject>();
            foreach (GameObject chip in chips)
            {
                if (getBetType(chip) == bet)
                {
                    removals.Add(chip);
                }
            }
            foreach (GameObject chip in removals)
            {
                chips.Remove(chip);
            }
        }

        private bool DiceDone()
        {
            return (!die1.alive && !die2.alive);
        }

        private void UpdateDice()
        {
            if (die1.alive)
            {
                if (die1.rectangle.Bottom >= background.rectangle.Bottom)
                {
                    die1.center.Y = background.rectangle.Bottom - die1.rectangle.Height / 2;
                    die1.velocity.Y *= -0.60f;
                }
                else if (die1.rectangle.Bottom <= background.rectangle.Bottom)
                {
                    die1.velocity.Y += accel;
                }
                die1.center += die1.velocity;
                die1.velocity *= .99f;
                die1.rotation += (float)rand.NextDouble() * 1.5f;

                if (die1.velocity.X <= 0.60f)
                {
                    die1.alive = false;
                    die1.velocity = Vector2.Zero;
                    die1.center.Y = background.rectangle.Bottom - die1.rectangle.Height / 2;
                    die1.rotation = 0.0f;
                }
            }

            if (die2.alive)
            {
                if (die2.rectangle.Bottom >= background.rectangle.Bottom)
                {
                    die2.center.Y = background.rectangle.Bottom - die2.rectangle.Height / 2;
                    die2.velocity.Y *= -0.60f;
                }
                else if (die2.rectangle.Bottom <= background.rectangle.Bottom)
                {
                    die2.velocity.Y += accel;
                }
                die2.center += die2.velocity;
                die2.velocity *= .99f;
                die2.rotation += (float)rand.NextDouble() * 1.5f;

                if (die2.velocity.X <= 0.60f)
                {
                    die2.alive = false;
                    die2.velocity = Vector2.Zero;
                    die2.center.X = die1.rectangle.Right + 4 + (die2.rectangle.Width / 2);
                    die2.center.Y = background.rectangle.Bottom - die2.rectangle.Height / 2;
                    die2.rotation = 0.0f;
                }
            }
        }

        private void RollDice()
        {
            die1num = rand.Next(6) + 1;
            die2num = rand.Next(6) + 1;
            die1.sprite = dice[die1num - 1].sprite;
            die1.center = Vector2.Zero;
            die2.sprite = dice[die2num - 1].sprite;
            die2.center = Vector2.Zero;

            die1.velocity = new Vector2(2.08f, 0);
            die2.velocity = new Vector2(2.5f, 0);

            die1.alive = true;
            die2.alive = true;

            die1.visible = true;
            die2.visible = true;
        }

        private void AddChip(Vector2 center)
        {
            try
            {
                if (5 <= bankroll)
                {
                    GameObject newChip = new GameObject(redchip);
                    newChip.center = center;
                    CrapsSystem.Bet type = getBetType(newChip);
                    if (type == CrapsSystem.Bet.passline && point != 0)
                        type = CrapsSystem.Bet.passlineodds;
                    if (crapsSystem.addBet(type, 5))
                    {
                        chips.Add(newChip);
                        bankroll -= 5;
                    }
                }
            }
            catch (Exception ex)
            {
                // TODO: Show message
            }
        }

        private CrapsSystem.Bet getBetType(GameObject newChip)
        {
            if ((passLineRect[0].Intersects(newChip.rectangle) ||
                passLineRect[1].Intersects(newChip.rectangle)))
            {
                return CrapsSystem.Bet.passline;
            }
            else if (comeRect.Intersects(newChip.rectangle))
            {
                return CrapsSystem.Bet.come;
            }
            else if (eightRect.Intersects(newChip.rectangle))
            {
                return CrapsSystem.Bet.eight;
            }
            else if (sixRect.Intersects(newChip.rectangle))
            {
                return CrapsSystem.Bet.six;
            }
            else
            {
                throw new Exception("Bet not acceptable");
            }
        }

        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);

            spriteBatch.Begin();

            DrawGameObject(background, spriteBatch);
            DrawChips(spriteBatch);
            DrawDice(spriteBatch);
            DrawText(spriteBatch);

            spriteBatch.End();

            base.Draw(gameTime);
        }

        private void DrawText(SpriteBatch spriteBatch)
        {
            spriteBatch.DrawString(font, String.Format("Point: {0}", point), Vector2.Zero, Color.Black);
            spriteBatch.DrawString(font, String.Format("Cash: {0}", bankroll), 
                new Vector2(0, background.rectangle.Bottom - 30), Color.Black);
        }

        private void DrawDice(SpriteBatch spriteBatch)
        {
            if (die1.visible)
            {
                DrawGameObject(die1, spriteBatch);
            }
            if (die2.visible)
            {
                DrawGameObject(die2, spriteBatch);
            }
        }

        private void DrawChips(SpriteBatch batch)
        {
            foreach (GameObject chip in chips)
            {
                DrawGameObject(chip, batch);
            }
        }

        public void DrawGameObject(GameObject go, SpriteBatch batch)
        {
            batch.Draw(go.sprite, go.center, null, Color.White, go.rotation, go.origin, go.scale,
                SpriteEffects.None, 0);
        }
    }
}
